package com.itheima.prize.api.action;

import com.alibaba.fastjson.JSON;
import com.itheima.prize.api.config.LuaScript;
import com.itheima.prize.commons.config.RabbitKeys;
import com.itheima.prize.commons.config.RedisKeys;
import com.itheima.prize.commons.db.entity.*;
import com.itheima.prize.commons.db.mapper.CardGameMapper;
import com.itheima.prize.commons.db.service.CardGameService;
import com.itheima.prize.commons.utils.ApiResult;
import com.itheima.prize.commons.utils.RedisUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.text.SimpleDateFormat;
import java.util.*;

@RestController
@RequestMapping("/api/act")
@Api(tags = {"抽奖模块"})
public class ActController {

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private LuaScript luaScript;

    @GetMapping("/limits/{gameid}")
    @ApiOperation(value = "剩余次数")
    @ApiImplicitParams({
            @ApiImplicitParam(name="gameid",value = "活动id",example = "1",required = true)
    })
    public ApiResult<Object> limits(@PathVariable int gameid, HttpServletRequest request){
        //获取活动基本信息
        CardGame game = (CardGame) redisUtil.get(RedisKeys.INFO+gameid);
        if (game == null){
            return new ApiResult<>(-1,"活动未加载",null);
        }
        //获取当前用户
        HttpSession session = request.getSession();
        CardUser user = (CardUser) session.getAttribute("user");
        if (user == null){
            return new ApiResult(-1,"未登陆",null);
        }
        //用户可抽奖次数
        Integer enter = (Integer) redisUtil.get(RedisKeys.USERENTER+gameid+"_"+user.getId());
        if (enter == null){
            enter = 0;
        }
        //根据会员等级，获取本活动允许的最大抽奖次数
        Integer maxenter = (Integer) redisUtil.hget(RedisKeys.MAXENTER+gameid,user.getLevel()+"");
        //如果没设置，默认为0，即：不限制次数
        maxenter = maxenter==null ? 0 : maxenter;

        //用户已中奖次数
        Integer count = (Integer) redisUtil.get(RedisKeys.USERHIT+gameid+"_"+user.getId());
        if (count == null){
            count = 0;
        }
        //根据会员等级，获取本活动允许的最大中奖数
        Integer maxcount = (Integer) redisUtil.hget(RedisKeys.MAXGOAL+gameid,user.getLevel()+"");
        //如果没设置，默认为0，即：不限制次数
        maxcount = maxcount==null ? 0 : maxcount;

        //幸运转盘类，先给用户随机剔除，再获取令牌，有就中，没有就说明抢光了
        //一般这种情况会设置足够的商品，卡在随机上
        Integer randomRate = (Integer) redisUtil.hget(RedisKeys.RANDOMRATE+gameid,user.getLevel()+"");
        if (randomRate == null){
            randomRate = 100;
        }

        Map map = new HashMap();
        map.put("maxenter",maxenter);
        map.put("enter",enter);
        map.put("maxcount",maxcount);
        map.put("count",count);
        map.put("randomRate",randomRate);

        return new ApiResult<>(1,"成功",map);
    }
    @GetMapping("/go/{gameid}")//这就像一个特殊的门牌号，当有人访问这个地址时，就会找到这个程序
    @ApiOperation(value = "抽奖")//给这个程序起个名字，告诉别人这是用来 "抽奖" 的
    @ApiImplicitParams({//告诉别人使用这个程序需要提供什么信息
            @ApiImplicitParam(name="gameid",value = "活动id",example = "1",required = true)//这里是说必须提供 "gameid"（活动的编号），比如可以填 1
    })
    public ApiResult<Object> act(@PathVariable int gameid, HttpServletRequest request){//定义了一个叫 act 的程序，它会处理抽奖的事情。它需要知道活动编号（gameid）和谁在请求（request）
        //TODO：任务6.1-抽奖业务-抽奖接口

        //1、根据当前时间判断活动是否开始
        CardGame game = (CardGame) redisUtil.get(RedisKeys.INFO+gameid);//从一个叫 redis 的地方，根据活动编号找这个活动的信息。就像根据学号从学校档案里找一个学生的资料
        if (game == null){//如果没找到，就执行下面的返回操作
            return new ApiResult<>(-1,"活动未开始",null);//当活动不存在时，告诉用户 "活动未开始"
        }
        //2、判断当前活动是否结束
        Date now = new Date();//记录下现在的时间，就像看一眼手表知道现在几点了
        if(now.before(game.getStarttime())){//检查现在的时间是不是在活动开始时间之前。就证明没有开始
            return new ApiResult<>(-1,"活动未开始",null);
        }
        if(now.after(game.getEndtime())){//检查现在的时间是不是已经过了活动结束时间。证明活动已结束
            return new ApiResult<>(-1,"活动已结束",null);
        }

        //3、判断用户是否登录
        HttpSession session = request.getSession();//就像你去游乐园时，工作人员给你一张门票，这行代码就是去拿这张 "门票"（session），用来识别你是谁
        CardUser user = (CardUser) session.getAttribute("user");//从这张 "门票" 上查看有没有写着你的信息（user），就像门票上印着你的名字
        if (user == null){//检查门票上有没有你的信息，如果没有（user == null）
            return new ApiResult<>(-1,"未登录",null);//就告诉你 "未登录"，不让你参加抽奖
        }

        //活动结束时间
        long end = game.getEndtime().getTime();//找到这个抽奖活动什么时候结束，把结束时间变成一个数字（方便计算）//.getTime()是因为变成毫秒值了
        //计算活动结束时间到现在还有多少秒，作为redis key过期时间
        long expire = (end - now.getTime()) / 1000;//算一算从现在到活动结束还有多少秒，这个时间会用来设置一个倒计时器

        // 从redis中获取用户等级配置
        String maxEnterKey = RedisKeys.MAXENTER+gameid; //最大抽奖数key//生成一个 "钥匙"，用来从 redis 里找这个活动允许抽多少次奖（最大抽奖数）
        String maxGoalKey = RedisKeys.MAXGOAL+gameid; //最大中奖数key//再生成一个 "钥匙"，用来找这个活动最多能中几次奖（最大中奖数）

        Integer maxEnter = (Integer) redisUtil.hget(maxEnterKey, user.getLevel() + "");//最大抽奖数//用上面的第一个钥匙，根据你的等级（比如青铜、白银），从 redis 里取出你最多能抽多少次奖
        //get方法需要第二个参数是字符串类型（就像查字典时，需要用 "词语" 作为关键词，而不能直接用数字）
        Integer maxGoal = (Integer) redisUtil.hget(maxGoalKey, user.getLevel() + "");//最大中奖数//用第二个钥匙，根据你的等级，取出你最多能中几次奖
        //你抽了几次奖的数字，和这个标签绑在一起存起来。比如你抽了 2 次，就会在系统里记着："抽奖次数_5_100" 对应 "2"
        String userEnterKey = RedisKeys.USERENTER+gameid+"_"+user.getId();//用户参与抽奖次数//生成一个专属于你的钥匙，用来记录你已经抽了多少次奖
        if (maxEnter != null){//如果最大抽奖数不为空
            //4、判断用户抽奖次数是否用完（前置检查）
            Integer userEnterCount = (Integer) redisUtil.get(userEnterKey);//用你的专属钥匙，查出你已经抽了多少次奖了
            if(userEnterCount!=null && userEnterCount.equals(maxEnter)){//如果你已经抽的次数等于最多能抽的次数（比如最多抽 3 次，你已经抽了 3 次）
                return new ApiResult<>(-1,"您的抽奖次数已用完",null);//就告诉你 "您的抽奖次数已用完"，不让你继续抽了
            }
        }

        maxGoal = maxGoal == null ? 0 : maxGoal;//最大中奖数不能为空

        //5、判断用户是否已经达到最大中奖次数（前置检查）
        String userGoalKey = RedisKeys.USERHIT+gameid+"_"+user.getId(); //用户中奖数key//生成一个专属于你的 "中奖记录标签"，格式和之前的抽奖次数标签类似
        Integer userGoalCount = (Integer) redisUtil.get(userGoalKey);//用上面的专属标签，从系统里查出你已经中了几次奖
        if(userGoalCount!=null && userGoalCount.equals(maxGoal)){//检查如果你已经中的次数，等于你最多能中的次数（比如最多中 2 次，你已经中了 2 次）
            return new ApiResult(-1,"您已达到最大中奖数",null);//就会告诉你 "您已达到最大中奖数"，不让你继续抽奖了
        }

        // 执行Lua脚本判断是否中奖并获取令牌
        Long token = luaScript.tokenCheck(gameid,user.getId(),maxGoal);
        // 更新用户抽奖次数
        redisUtil.incr(userEnterKey, 1);//这行是给用户的 “参与次数” 加 1
        redisUtil.set(RedisKeys.USERGAME+gameid+"_"+user.getId(),true);//这行是在 Redis 里做一个 “标记”，表示 “这个用户已经参加过这个活动了”
        //redisUtil.expire(userEnterKey, expire);
        CardUserGame cardUserGame = new CardUserGame();//新建一个CardUserGame对象（可以理解为一张 “用户活动参与表” 的记录）
        cardUserGame.setUserid(user.getId());// 记录是哪个用户（用户ID）
        cardUserGame.setGameid(gameid);// 记录参加的是哪个活动（活动ID）
        cardUserGame.setCreatetime(new Date());// 记录当前参与时间（现在的时间）

        //Map<String, Object> playMessage = new HashMap<>();
        //playMessage.put("cardUserGame", cardUserGame);
        String playMessageJson = JSON.toJSONString(cardUserGame);// 把参与记录对象转成JSON字符串（方便传输）
        rabbitTemplate.convertAndSend(// 通过RabbitMQ发送消息
                RabbitKeys.EXCHANGE_DIRECT,// 消息交换机名称
                RabbitKeys.QUEUE_PLAY,// 消息队列名称
                playMessageJson// 要发送的内容（用户参与记录）
        );

        if(token == 0){ //7、判断用户是否中奖
            return new ApiResult(0,"未中奖",null);
        }else if(token == -1){ //可用抽奖次数不足
            return new ApiResult(-1,"您已达到最大中奖数",null);
        }else if(token == -2){ //6、判断奖品是否已经抽光
            return new ApiResult(-1,"奖品已抽光",null);
        }

        //8、用户中奖，可以取走奖品。

        // 更新用户中奖次数
        redisUtil.incr(RedisKeys.USERHIT+gameid+"_"+user.getId(), 1); //用户在这个活动中总共中了几次奖
        redisUtil.expire(userEnterKey, expire);//userEnterKey 是前面记录 “用户参与次数” 的 Redis 键//expire 是一个时间值//表示过了这个时间后，Redis 会自动删除这条参与次数记录。

        //8.1 根据令牌获取奖品信息
        String tokenKey = RedisKeys.TOKEN + gameid + "_" + token;//token不是-1，0，-2，是奖品的令牌，用这个令牌可以获取奖品信息
        CardProduct product = (CardProduct) redisUtil.get(tokenKey);//用这个令牌，从 Redis 缓存里取出奖品信息

        // 10、准备消息队列数据 - 中奖记录   //创建一条用户的中奖记录，并把它转换成 JSON 格式，为后续通过消息队列发送做准备。
        CardUserHit cardUserHit = new CardUserHit();//CardUserHit 可以理解为一个 “中奖记录表” 的模板，专门用来存放用户中奖的详细信息。
        cardUserHit.setGameid(gameid);// 记录中奖的活动ID（比如“双11抽奖活动”的ID是100）
        cardUserHit.setUserid(user.getId());// 记录中奖用户的ID（比如“用户张三”的ID是200）
        cardUserHit.setProductid(product.getId());//记录中了什么奖品（比如“华为手机”的ID是300）
        cardUserHit.setHittime(new Date());//记录中奖时间（就是当前时间）

        String hitMessageJson = JSON.toJSONString(cardUserHit);//JSON.toJSONString(...) 是把前面填好的 “中奖记录对象” 转换成 JSON 格式的字符串（一种通用的数据格式，方便在不同系统间传输）。

        //8.2 异步发送中奖消息到RabbitMQ
        // RabbitMq传输对象的时候，使用FastJson将对象转为字符串后传输，可以避免很多问题，接收到消息后，再转回对应对象。
        // 抽奖接口中涉及到两个队列：prize_queue_play：用户参加的活动通过该队列投放； prize_queue_hit：用户中奖后的信息及中的奖品通过该队列投放
        rabbitTemplate.convertAndSend(
                RabbitKeys.EXCHANGE_DIRECT,// 消息交换机名称（类似邮局的分拣中心
                RabbitKeys.QUEUE_HIT,// 消息队列名称（专门接收中奖信息的"信箱"）
                hitMessageJson);// 要发送的内容（前面转换好的中奖记录JSON）
        //rabbitTemplate.convertAndSend(RabbitKeys.EXCHANGE_DIRECT,RabbitKeys.QUEUE_PLAY,playMessageJson);

        //8.2 给用户提示中奖
        return new ApiResult<>(1,"恭喜中奖",product);

    }

    @GetMapping("/info/{gameid}")
    @ApiOperation(value = "缓存信息")
    @ApiImplicitParams({
            @ApiImplicitParam(name="gameid",value = "活动id",example = "1",required = true)
    })
    public ApiResult info(@PathVariable int gameid){//从 URL 路径中获取gameid参数，类型为 int
        //TODO：任务5.2-缓存预热-查询验证缓存
        Map<String,Object> resultMap = new HashMap<>();

        //1、查询活动基本信息
        String gameInfoKey = RedisKeys.INFO + gameid;//生成一个缓存键（类似字典的钥匙），格式是 "固定前缀 INFO + 活动 ID"
        Object gameInfo = redisUtil.get(gameInfoKey);//用上面生成的键，去 Redis 缓存里查找对应的活动信息
        if(gameInfo!=null){
            resultMap.put(gameInfoKey,gameInfo);
        }

        // 2. 查询令牌列表 - 使用List类型存储
        String tokensKey = RedisKeys.TOKENS + gameid;
        List<Object> tokenList = redisUtil.lrange(tokensKey, 0, -1);//从 Redis 中获取这个活动的所有令牌//lrange是获取列表范围内的元素，0表示从第一个开始，-1表示到最后一个结束
        if (tokenList != null && !tokenList.isEmpty()) {//检查是否获取到了令牌列表，并且列表不为空
            Map<String, Object> tokensMap = new LinkedHashMap<>();

            for (Object tokenObj : tokenList) {
                long token = Long.parseLong(tokenObj.toString());//把令牌对象转换成字符串，再转成数字类型（long），方便后续计算
                // 还原真正的时间戳（除以1000）
                long realTimestamp = token / 1000;

                // 查询对应的奖品信息
                String tokenItemKey = RedisKeys.TOKEN + gameid + "_" + token;
                Object productInfo = redisUtil.get(tokenItemKey);//用上面的键去 Redis 中查询，获取这个令牌对应的奖品信息

                if (productInfo != null) {
                    // 将时间戳转换为可读日期格式
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");//创建一个日期格式化工具，指定格式为 "年 - 月 - 日 时：分: 秒。毫秒"
                    String dateStr = sdf.format(new Date(realTimestamp));//把还原后的时间戳转换成上面指定格式的字符串//比如把1620000000000转成2021-05-03 12:00:00.000
                    tokensMap.put(dateStr, productInfo);//时间对应奖品//把 "格式化的时间字符串" 和 "对应的奖品信息" 放进tokensMap里
                }
            }

            resultMap.put(tokensKey, tokensMap);//最后把整理好的tokensMap放进总的结果容器resultMap里
        }

        // 3. 查询最大目标值配置 - 使用Hash类型存储
        String maxGoalKey = RedisKeys.MAXGOAL + gameid;//生成查询 "最大中奖次数" 配置的缓存键，格式是 "固定前缀 MAXGOAL + 活动 ID"
        Map<Object, Object> maxGoalMap = redisUtil.hmget(maxGoalKey);//hmget是获取哈希表（Hash 类型）的所有数据，里面存着 "用户等级" 和 "对应最大中奖次数" 的键值对
        if (maxGoalMap != null && !maxGoalMap.isEmpty()) {
            Map<String, Object> convertedMaxGoal = new LinkedHashMap<>();//创建一个新的convertedMaxGoal地图（用 LinkedHashMap 保持顺序）//用来存放转换格式后的 "最大中奖次数" 配置
            for (Map.Entry<Object, Object> entry : maxGoalMap.entrySet()) {//遍历从 Redis 中拿到的每个配置项（entry 就是一个键值对，比如 "等级 3" 对应 "最多中奖 2 次"）
                convertedMaxGoal.put(entry.getKey().toString(), entry.getValue());//把键（用户等级）从 Object 类型转成字符串类型，值保持不变
            }
            resultMap.put(maxGoalKey, convertedMaxGoal);//把转换好的 "最大中奖次数" 配置放进总的结果容器resultMap里
        }

        // 4. 查询最大参与值配置 - 使用Hash类型存储
        String maxEnterKey = RedisKeys.MAXENTER + gameid;
        Map<Object, Object> maxEnterMap = redisUtil.hmget(maxEnterKey);
        if (maxEnterMap != null && !maxEnterMap.isEmpty()) {
            Map<String, Object> convertedMaxEnter = new LinkedHashMap<>();
            for (Map.Entry<Object, Object> entry : maxEnterMap.entrySet()) {
                convertedMaxEnter.put(entry.getKey().toString(), entry.getValue());
            }
            resultMap.put(maxEnterKey, convertedMaxEnter);
        }

        return new ApiResult(200,"缓存信息",resultMap);


    }
}